import { Component } from '@angular/core';
import { NzModalService, NzModalRef, NzNotificationService, NzTreeNode } from 'ng-zorro-antd';
import { zip } from 'rxjs';
import { SiteService } from '@srvs/site.service';
import { ApiService } from '@srvs/api.service';

import { EditRoleComponent } from './edit-role/edit-role.component';

@Component({
  selector: 'role-management',
  templateUrl: './role-management.component.html',
  styleUrls: ['./role-management.component.less']
})
export class RoleManagementComponent {

  public data: any = {
    roles: [],
    menu: []
  };
  public params: any = {
    siteId: null,
    roleId: null
  };
  private currentRoleTools: Element;

  constructor(
    public siteSrv: SiteService,
    private modalSrv: NzModalService,
    private msgSrv: NzNotificationService,
    private apiSrv: ApiService
  ) {
    const site: any = this.siteSrv.site || {};
    this.params.siteId = site.id;
    this.load();
  }

  public _onReuseInit(): void {
    this.load();
  }

  /**
   * 加载选中站点角色、菜单、权限列表
   */
  public load(): void {
    const apiRoles: string = `/api/sites/${this.params.siteId}/roles`,
      apiMenu: string = `/api/sites/${this.params.siteId}/menus`;
    zip(
      this.apiSrv.get(apiRoles),
      this.apiSrv.get(apiMenu)
    ).subscribe(
      ([roles, menu]: any) => {
        this.data.roles = roles;
        this.generateTreeNodes(menu);
        if (roles.length) {
          this.params.roleId = null;
          this.changeRole(roles[0].id);
        } else {
          this.refreshRoleMenuAccess([])
        };
      },
      error => {
        const errorTitle: string = error.url.includes(apiRoles)
          ? '加载角色列表失败！'
          : '加载站点菜单失败！';
        this.msgSrv.error(errorTitle, error.message);
      }
    );
  }

  /**
   * 改变站点
   */
  public changeSite(value: [number, number]): void {
    this.params.siteId = value[1];
    this.load();
  }

  /**
   * 创建角色
   */
  public createRole(): void {
    const ref: NzModalRef = this.modalSrv.create({
      nzTitle: '创建角色',
      nzContent: EditRoleComponent,
      nzWidth: 576,
      nzMaskClosable: false,
      nzOnOk: (component: EditRoleComponent) => {
        ref.getInstance().nzOkLoading = true;
        component.submit().subscribe(
          data => {
            this.data.roles = [...this.data.roles, data];
            this.msgSrv.success('创建角色成功！', null);
            ref.destroy();
          },
          error => {
            this.msgSrv.error('创建角色失败！', error.message);
            ref.getInstance().nzOkLoading = false;
          },
          () => ref.getInstance().nzOkLoading = false
        );
        return false;
      }
    });
  }

  /**
   * 修改角色
   * @param role 角色数据
   * @param event 点击事件对象
   */
  public updateRole(role: any, event: Event): void {
    event.stopPropagation();
    const ref: NzModalRef = this.modalSrv.create({
      nzTitle: '修改角色',
      nzContent: EditRoleComponent,
      nzComponentParams: { data: role },
      nzWidth: 576,
      nzMaskClosable: false,
      nzOnOk: (component: EditRoleComponent) => {
        ref.getInstance().nzOkLoading = true;
        component.submit().subscribe(
          data => {
            const index: number = this.data.roles.findIndex(item => item.id === role.id),
              roles: any[] = [...this.data.roles];
            roles[index] = data;
            this.data.roles = roles;
            this.msgSrv.success('修改角色成功！', null);
            ref.destroy();
          },
          error => {
            this.msgSrv.error('修改角色失败！', error.message);
            ref.getInstance().nzOkLoading = false;
          },
          () => ref.getInstance().nzOkLoading = false
        );
        return false;
      }
    });
  }

  /**
   * 删除角色
   * @param role 角色数据
   */
  public deleteRole(role: any): void {
    const deleteRole: string = `/api/roles/${role.id}`;
    this.apiSrv.delete(deleteRole).subscribe(
      () => {
        this.data.roles = this.data.roles.filter(item => item !== role);
        this.msgSrv.success('删除角色成功！', null);
      },
      error => {
        this.msgSrv.error('删除角色失败！', error.message);
      }
    );
  }

  /**
   * 改变角色
   * @param roleId 角色id
   */
  public changeRole(roleId: number): void {
    if (this.params.roleId !== roleId) {
      this.params.roleId = roleId;
      const apiRoleMenu = `/api/roles/${this.params.roleId}/menus`;
      this.apiSrv.get(apiRoleMenu).subscribe(
        (roleMenu: any[]) => this.refreshRoleMenuAccess(roleMenu),
        error => this.msgSrv.error('角色菜单权限加载失败！', error.message)
      );
    }
  }

  /**
   * 保存角色权限
   */
  public save(): void {
    const apiRole = `/api/roles/${this.params.roleId}/menus`,
      menu: any[] = [];
    this.everyMenuItem(item => {
      if (
        item.isChecked ||
        item.isAllChecked ||
        item.isHalfChecked
      ) menu.push(item.origin);
    });
    this.apiSrv.put(apiRole, menu).subscribe(
      (roleMenu: any[]) => {
        this.refreshRoleMenuAccess(roleMenu);
        this.msgSrv.success('保存角色权限成功！', null);
      },
      error => this.msgSrv.error('保存角色权限失败！', error.message)
    );
  }

  /**
   * 更新角色菜单权限
   * @param menu 平面菜单数组
   */
  private refreshRoleMenuAccess(menu: any[]): void {
    const roleMenu = {};
    menu.forEach(item => roleMenu[item.id] = item);

    this.everyMenuItem(item => {
      item.isChecked = false;
      item.isAllChecked = false;
      item.isHalfChecked = false;

      if (item.isLeaf) {
        if (roleMenu[item.key]) {
          item.isChecked = true;
          item.isAllChecked = true;
          item.parentNode.childCheckCount = item.parentNode.childCheckCount || 0;
          item.parentNode.childCheckCount++;
        }
      } else {
        if (item.childCheckCount === item.children.length) {
          item.isChecked = true;
          item.isAllChecked = true;
          item.parentNode.childCheckCount = item.parentNode.childCheckCount || 0;
          item.parentNode.childCheckCount++;
        } else if (item.childCheckCount || item.childHalfCheckCount) {
          item.isHalfChecked = true;
          item.parentNode.childHalfCheckCount = item.parentNode.childHalfCheckCount || 0;
          item.parentNode.childHalfCheckCount++;
        }
        delete item.childCheckCount;
        delete item.childHalfCheckCount;
      }
    }, true);

    const root: NzTreeNode | any = this.data.menu[0];
    root.isChecked = false;
    root.isAllChecked = false;
    root.isHalfChecked = false;
    if (root.childCheckCount === root.children.length) {
      root.isChecked = true;
      root.isAllChecked = true;
    } else if (root.childCheckCount || root.childHalfCheckCount) {
      root.isHalfChecked = true;
    }
    delete root.childCheckCount;
    delete root.childHalfCheckCount;
  }

  /**
   * 生成树形菜单
   * @param menu 平面菜单数组
   */
  private generateTreeNodes(menu: any[]): void {
    const tiers: any = {},
      treeOptions: any = {
        title: 'root',
        key: 'root',
        selectable: false,
        i18n: 'root',
        children: []
      };

    if (Array.isArray(menu)) {
      menu
        .map(item => {
          const treeNodeOptions: any = Object.assign({ title: item.menuName, key: item.id }, item),
            nodeIndexArr: string[] = item.nodeIndex.split('.'),
            parentNodeIndex: string = nodeIndexArr.slice(0, -1).join('.'),
            nodeIndex: string = nodeIndexArr[nodeIndexArr.length - 1];

          if (parentNodeIndex) {
            tiers[parentNodeIndex] = tiers[parentNodeIndex] || [];
            tiers[parentNodeIndex].splice(parseInt(nodeIndex), 0, treeNodeOptions);
          } else {
            treeOptions.children.splice(parseInt(nodeIndex), 0, treeNodeOptions);
          }

          return treeNodeOptions;
        })
        .forEach((item: any) => {
          if (tiers[item.nodeIndex]) {
            item.children = tiers[item.nodeIndex];
          } else {
            item.isLeaf = true;
          }
        });
    }

    this.data.menu = [new NzTreeNode(treeOptions)];
  }

  /**
   * 遍历每个菜单
   * @param callback 遍历回调
   * @param parentAfter 父菜单后遍历
   * @param startMenu 起始菜单
   */
  private everyMenuItem(callback: Function, parentAfter?: boolean, startMenu?: NzTreeNode): void {
    startMenu = startMenu || this.data.menu[0];
    startMenu.children.forEach(item => {
      if (!parentAfter) callback(item);
      if (!item.isLeaf) this.everyMenuItem(callback, parentAfter, item);
      if (parentAfter) callback(item);
    });
  }

}
